﻿using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading.Tasks;

namespace MyHome.Server.PluginBase
{
    /// <summary>
    /// Abstract base class for all input-output plugins providing some generic implementation.
    /// </summary>
    /// <typeparam name="T">The type of data used in the output part of the plugin.</typeparam>
    /// <typeparam name="U">The type of data used in the intput part of the plugin.</typeparam>
    /// <remarks>
    /// Note that you pass data into this plugin by using the output part (you are passing data OUT of the system into the plugin).
    /// Also note that you pass data out of this plugin by using the input part (you are passing data out of the plugin INTO of the system).
    /// </remarks>
    public abstract class InputOutputPlugin<T, U> : Plugin, IInputOutputPlugin<T, U>
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="InputOutputPlugin{T, U}"/> class.
        /// </summary>
        protected InputOutputPlugin()
            : base()
        {
            InputChanged += InputPlugin_InputChanged;
        }

        /// <summary>
        /// Gets the type of the plugin.
        /// </summary>
        /// <value>
        /// <see cref="PluginType.InputOutput"/>
        /// </value>
        public override PluginType PluginType
        {
            get { return PluginType.InputOutput; }
        }

        #region InputPlugin

        /// <summary>
        /// Occurs when new input data is available.
        /// </summary>
        public event EventHandler<ValueChangedEventArgs<U>> InputChanged;

        /// <summary>
        /// Gets the last value of data that was generated by this plugin.
        /// </summary>
        /// <value>
        /// The last value.
        /// </value>
        public U LastValue { get; private set; }

        /// <summary>
        /// Call this method when new data is available.
        /// This will raise the InputChanged event.
        /// </summary>
        /// <param name="newValue">The new value.</param>
        protected void OnNewValue(U newValue)
        {
            Trace.TraceInformation("{0} \"{1}\", Id: {2} has new input: {3}", PluginType.ToString(), this, Id, newValue);
            InputChanged(this, new ValueChangedEventArgs<U>(LastValue, newValue));
        }

        private void InputPlugin_InputChanged(object sender, ValueChangedEventArgs<U> e)
        {
            LastValue = e.NewValue;
        }

        #endregion InputPlugin

        #region Output

        /// <summary>
        /// Sends the output asynchronous.
        /// </summary>
        /// <param name="value">The value of data to output.</param>
        /// <returns>A value indicating the success of this operation.</returns>
        public async Task<bool> SendOutputAsync(T value)
        {
            try
            {
                await Task.Run(() => OnSendOutput(value));
                Trace.TraceInformation("{0} \"{1}\", Id: {2} sent new output: {3}", PluginType.ToString(), this, Id, value);
                return true;
            }
            catch
            {
                Trace.TraceError("{0} \"{1}\", Id: {2} failed to sent new output: {3}", PluginType.ToString(), this, Id, value);
                State = PluginState.Error;
                return false;
            }
        }

        /// <summary>
        /// Called when data to output is received.
        /// </summary>
        /// <param name="value">The value to output.</param>
        protected abstract void OnSendOutput(T value);

        #endregion Output
    }
}